package main

import (
	"context"
	"encoding/json"

	"github.com/gin-gonic/gin"
	"github.com/gogo/protobuf/proto"
	tmbytes "github.com/tendermint/tendermint/libs/bytes"
	"github.com/tendermint/tendermint/my_test/config"
	u "github.com/tendermint/tendermint/my_test/web/utils"
	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
	"github.com/tendermint/tendermint/rpc/client/http"
	"github.com/tendermint/tendermint/types"
)

var cli *http.HTTP

func init() {
	var err error
	cli, err = http.New(config.DefaultIPAndPort, "/websocket")
	if err != nil {
		panic(err)
	}
}

type MerkleProof struct {
	TxProof  types.TxProof    `json:"tx_proof"`
	RootHash tmbytes.HexBytes `json:"root_hash"`
}

type ProvResult struct {
	Txs             []*tmproto.Tx  `json:"txs"`
	MerkleProofList []*MerkleProof `json:"merkle_proof_list"`
	// CumulativeHash  []byte         `json:"cumulative_hash"`
}

func searchByHeight(heightList []int64, objectId string, dataType int, cumulativeHash []byte) (*ProvResult, error) {
	provResult := new(ProvResult)
	// provResult.CumulativeHash = cumulativeHash

	for i := 0; i < len(heightList); i++ {
		if i-1 >= 0 && heightList[i-1] == heightList[i] {
			continue
		}

		h := heightList[i]
		blockRes, err := cli.Block(context.TODO(), &h)
		if err != nil {
			return nil, err
		}

		for index := 0; index < len(blockRes.Block.Txs); index++ {
			var tx tmproto.Tx
			if err = proto.Unmarshal(blockRes.Block.Txs[index], &tx); err != nil {
				return nil, err
			}

			var data tmproto.FileData
			if err = proto.Unmarshal(tx.Data, &data); err != nil {
				return nil, err
			}

			if data.ObjectId == objectId && tx.DataType == tmproto.DataType(dataType) {
				proof := blockRes.Block.Txs.Proof(index)

				tx.Data = nil

				provResult.Txs = append(provResult.Txs, &tx)
				provResult.MerkleProofList = append(provResult.MerkleProofList, &MerkleProof{
					TxProof:  proof,
					RootHash: blockRes.Block.DataHash,
				})
			}
		}
	}

	return provResult, nil
}

func NewRouter() *gin.Engine {
	e := gin.Default()

	e.POST("/prov_verification", func(ctx *gin.Context) {
		provResult := new(ProvResult)

		decoder := json.NewDecoder(ctx.Request.Body)
		if err := decoder.Decode(provResult); err != nil {
			ctx.JSON(200, gin.H{
				"code": 1,
				"msg":  err.Error(),
			})
			return
		}

		for i := 0; i < len(provResult.MerkleProofList); i++ {
			proof := provResult.MerkleProofList[i]
			if err := proof.TxProof.Validate(proof.RootHash); err != nil {
				ctx.JSON(200, gin.H{
					"code": 1,
					"msg":  err.Error(),
				})
				return
			}
		}

		//pbTxList := make([][]byte, 0, len(provResult.Txs))
		//for _, tx := range provResult.Txs {
		//	t, err := proto.Marshal(tx)
		//	if err != nil {
		//		ctx.JSON(200, gin.H{
		//			"code": 1,
		//			"msg":  err.Error(),
		//		})
		//		return
		//	}
		//	pbTxList = append(pbTxList, t)
		//}

		//if err := utils.Verify(pbTxList, provResult.CumulativeHash); err != nil {
		//	ctx.JSON(200, gin.H{
		//		"code": 1,
		//		"msg":  err.Error(),
		//	})
		//	return
		//}

		ctx.JSON(200, gin.H{
			"code": 0,
			"msg":  "verification success",
		})
	})

	e.POST("/prov", func(ctx *gin.Context) {
		t := struct {
			ObjectId string `json:"object_id"`
			DataType int    `json:"data_type"`
		}{}

		decoder := json.NewDecoder(ctx.Request.Body)
		if err := decoder.Decode(&t); err != nil {
			ctx.JSON(200, gin.H{
				"code":    1,
				"message": err.Error(),
			})
			return
		}

		var (
			heightList     []int64
			cumulativeHash []byte
		)

		abciQueryRes, err := cli.ABCIQuery(context.TODO(), "", nil)
		if err != nil {
			panic(err)
		}

		blockRes, err := cli.Block(ctx, &abciQueryRes.Response.Height)
		if err != nil {
			panic(err)
		}

		mptQueryRes, err := cli.MPTQuery(ctx, blockRes.Block.Header.MPTHash, t.ObjectId)
		if err != nil {
			panic(err)
		}

		if mptQueryRes != nil {
			heightList = mptQueryRes.HeightList
			cumulativeHash = mptQueryRes.CumulativeHash
		}

		provResult, err := searchByHeight(heightList, t.ObjectId, t.DataType, cumulativeHash)

		if err != nil {
			ctx.JSON(200, gin.H{
				"code":    1,
				"message": err.Error(),
			})
		} else {
			ctx.JSON(200, gin.H{
				"code":    0,
				"data":    provResult,
				"message": "success",
			})
		}
	})

	e.POST("/submit_tx", func(c *gin.Context) {
		for h := 1; h <= config.Height; h++ {
			for i := 0; i < config.TxNum; i++ {
				pbTx, err := u.GenerateTx(tmproto.DataType_IMAGE)
				if err != nil {
					c.JSON(200, gin.H{
						"code":    1,
						"message": err.Error(),
					})
					return
				}

				_, err = cli.BroadcastTxAsync(context.TODO(), pbTx)
				if err != nil {
					c.JSON(200, gin.H{
						"code":    1,
						"message": err.Error(),
					})
					return
				}

				pbTx, err = u.GenerateTx(tmproto.DataType_SERIES)
				if err != nil {
					c.JSON(200, gin.H{
						"code":    1,
						"message": err.Error(),
					})
					return
				}

				_, err = cli.BroadcastTxAsync(context.TODO(), pbTx)
				if err != nil {
					c.JSON(200, gin.H{
						"code":    1,
						"message": err.Error(),
					})
					return
				}

				pbTx, err = u.GenerateTx(tmproto.DataType_TEXT)
				if err != nil {
					c.JSON(200, gin.H{
						"code":    1,
						"message": err.Error(),
					})
					return
				}

				_, err = cli.BroadcastTxAsync(context.TODO(), pbTx)
				if err != nil {
					c.JSON(200, gin.H{
						"code":    1,
						"message": err.Error(),
					})
					return
				}
			}
		}

		c.JSON(200, gin.H{
			"code":    0,
			"message": "success",
		})
	})

	return e
}

func main() {
	e := NewRouter()
	if err := e.Run(":9190"); err != nil {
		panic(err)
	}
}
